package world;

import org.lwjgl.opengl.GL11;

import math.EulerAngles;
import math.Math3D;
import math.Matrix4;
import math.Vector3f;

public class Camera {
	// Moving directions
	private float fov, width, height, zNear, zFar;
	private Matrix4 projectionMatrix, cameraMatrix;
	
	private Vector3f position;
	private EulerAngles rotation;
	private float targetDistance;
	private World world;
	private boolean flyMode;

	public Camera( float fovRad, float width, float height, float zNear, float zFar) {
		this.fov = fovRad;
		this.width = width;
		this.height = height;
		this.zNear = zNear;
		this.zFar = zFar;
		this.projectionMatrix = (new Matrix4()).clearToPerspective(fov * (float) Math.PI / 180, width, height, zNear, zFar);
		
		GL11.glViewport(0, 0, (int) width, (int)height);
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GL11.glLoadMatrix(projectionMatrix.toBuffer());
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
	}
	public Matrix4 getProjectionMatrix(){
		return projectionMatrix;
	}
	
	/* Use this when adding rotation instead of Vector3f.add since this function makes the numbers stay under 360. */
	public void rotate(EulerAngles rot) {
		rotation.rotate(rot);
	}
	
	public void moveForward(float delta) {
		float headingRadians = rotation.getHeadingRadians();
		float newX = position.x + (float) Math.sin(headingRadians) * delta;
		float newZ = position.z - (float) Math.cos(headingRadians) * delta;
		float newY = position.y;
		if(flyMode){
			newY = newY - 0.2f;
		}
		moveByCheckCollision(newX, newY, newZ);
	}

	public void moveBackwards(float delta) {
		float headingRadians = rotation.getHeadingRadians();
		float newX = position.x - (float) Math.sin(headingRadians) * delta;
		float newZ = position.z + (float) Math.cos(headingRadians) * delta;
		float newY = position.y - 0.2f;
		moveByCheckCollision(newX, newY, newZ);
	}

	public void moveLeft(float delta) {
		float headingRadians = rotation.getHeadingRadians();
		float newX = position.x - (float)Math.sin(headingRadians + Math3D.R_PI_HALF) * delta;
		float newZ = position.z + (float)Math.cos(headingRadians + Math3D.R_PI_HALF) * delta;
		float newY = position.y - 0.2f;
		moveByCheckCollision(newX, newY, newZ);		
	}

	public void moveRight(float delta) {
		float headingRadians = rotation.getHeadingRadians();
		float newX = position.x + (float)Math.sin(headingRadians + Math3D.R_PI_HALF) * delta;
		float newZ = position.z - (float)Math.cos(headingRadians + Math3D.R_PI_HALF) * delta;
		float newY = position.y - 0.2f;
		moveByCheckCollision(newX, newY, newZ);
	}
	
	public void moveUp(float distance) {
		float newY = position.y+ 0.2f;
		moveByCheckCollision(position.x, newY, position.z);
	}

	public void moveDown(float distance) {
		float newY = position.y - 0.2f;
		moveByCheckCollision(position.x, newY, position.z);
	}

	public void pitch(float degree) {
		rotation.pitch(degree);
	}

	public void heading(float degree) {
		rotation.heading(degree);
	}

	public Matrix4 calcLookAtMatrix() {
		return rotation.toLookAtMatrix().translate(new Vector3f(position).mult(-1));		
	}
	
	private void moveByCheckCollision(float newX, float newY, float newZ) {
		if (!world.solidAt(newX, position.y, position.z)) {
			position.x = newX;
		}

		if (!world.solidAt(position.x, position.y, newZ)) {
			position.z = newZ;
		}

		if (!world.solidAt(position.x, newY, position.z)) {
			position.y = newY;
			flyMode = true;
		} else {
			flyMode = false;
		}
	}
	
	

/*
	private Vector3f resolveTargetPosition() {
		return rotation.toDirection().mult(targetDistance).add(position);
	}	
*/
	public void setWorld(World world,Vector3f position, EulerAngles rotation){
		this.world = world;
		this.position = position;
		this.rotation = rotation;
		this.targetDistance = 10;
	}
	
	public Vector3f getPosition(){
		return position;
	}	
}